package controle;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.PriorityQueue;

import vista.Janela;

import extra.Constantes;
import extra.Gerador;

import modelo.Evento;
import modelo.Host;


public class Simulator {
	
	private static FileWriter log;
	private static BufferedWriter escritor;
	//Lista de hosts que farao parte da simulacao
	private static ArrayList<Host> listaHosts = new ArrayList<Host>();
	//Tempo em milisegundos que cada rodada ira durar
	private double tempoRodada;
	//Numero de rodadas que serao feitas
	private int numeroRodadas;
	//Lista dos eventos agendados durante uma rodada
	private static PriorityQueue<Evento> listaEventos = new PriorityQueue<Evento>();;
	//Tempo atual em milisegundos, vai sendo incrementado de acordo com a lista de eventos
	static double tempoAtual;
	//Identifica quando o Simulador esta na fase transiente
	static boolean faseTransiente;
	private ArrayList<ArrayList<Double>> tabelaResultadosTap;
	private ArrayList<ArrayList<Double>> tabelaResultadosTam;
	private ArrayList<ArrayList<Double>> tabelaResultadosNcm;
	private ArrayList<ArrayList<Double>> tabelaResultadosVazao;
	
	public Simulator(ArrayList<Host> listaHosts, double tempoRodada, int numeroRodadas) {
		this.setListaHosts(listaHosts);
		this.setTempoRodada(tempoRodada);
		this.setNumeroRodadas(numeroRodadas);
		setTempoAtual(0.0);
		setFaseTransiente(true);
		this.setTabelaResultadosTap(new ArrayList<ArrayList<Double>>());
		this.setTabelaResultadosTam(new ArrayList<ArrayList<Double>>());
		this.setTabelaResultadosNcm(new ArrayList<ArrayList<Double>>());
		this.setTabelaResultadosVazao(new ArrayList<ArrayList<Double>>());
	}
	
	public void iniciaSimulacao() {
		long inicioSimulacao;
		long fimSimulacao;
		for (Host host : getListaHosts()) {
			if (host.getTempoMedioEntreChegadas() != -1) {
				adicionaEvento(new Evento(Gerador.geraIdentificador(), host.getTempoProximaChegada(getTempoAtual()), host, Evento.tipos.ChegadaMensagem));
			}
		}
		
		if (Janela.isDebugging()) {
			try {
				log = new FileWriter(new File("./log.txt"),false);
				escritor = new BufferedWriter(log);
				if(Janela.isDebugging()) Simulator.imprime("INICIANDO SIMULACAO COM OS SEGUINTES HOSTS: " + getListaHosts());
				inicioSimulacao = System.currentTimeMillis();
				this.executaSimulacao();
				fimSimulacao = System.currentTimeMillis();
				Simulator.imprime("\n\n----> FIM SIMULACAO <----\n\n");
				
				Janela.imprime("***************************************************************** ESTATÍSTICAS ******************************************************************");
				Simulator.imprime("***************************************************************** ESTATÍSTICAS ******************************************************************");
				
				Janela.imprime("TEMPO DE EXECUÇÃO: " + (fimSimulacao - inicioSimulacao) + " MILISEGUNDOS\n");
				Simulator.imprime("TEMPO DE EXECUÇÃO: " + (fimSimulacao - inicioSimulacao) + " MILISEGUNDOS\n");
				
				Janela.imprime("TODOS OS TEMPOS ESTÃO EXPRESSOS EM MILISEGUNDOS\n");
				Simulator.imprime("TODOS OS TEMPOS ESTÃO EXPRESSOS EM MILISEGUNDOS\n");
				
				Janela.imprime(this.geraTabelaTap());
				Simulator.imprime(this.geraTabelaTap());
				
				Janela.imprime(this.geraTabelaTam());
				Simulator.imprime(this.geraTabelaTam());
				
				Janela.imprime(this.geraTabelaNcm());
				Simulator.imprime(this.geraTabelaNcm());
				
				Janela.imprime(this.imprimeUtilizacao());
				Simulator.imprime(this.imprimeUtilizacao());
				
				Janela.imprime(this.geraTabelaVazao());
				Simulator.imprime(this.geraTabelaVazao());
				
				Janela.imprime("**************************************************************************************************************************************************");
				Simulator.imprime("**************************************************************************************************************************************************");
				escritor.close();
				
				
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		else {
			inicioSimulacao = System.currentTimeMillis();
			this.executaSimulacao();
			fimSimulacao = System.currentTimeMillis();
			
			Janela.imprime("***************************************************************** ESTATÍSTICAS ******************************************************************");
			
			Janela.imprime("TEMPO DE EXECUÇÃO: " + (fimSimulacao - inicioSimulacao) + " MILISEGUNDOS\n");
			Janela.imprime("TODOS OS TEMPOS ESTÃO EXPRESSOS EM MILISEGUNDOS\n");
			Janela.imprime(this.geraTabelaTap());
			Janela.imprime(this.geraTabelaTam());
			Janela.imprime(this.geraTabelaNcm());
			Janela.imprime(this.imprimeUtilizacao());
			Janela.imprime(this.geraTabelaVazao());
			
			Janela.imprime("**************************************************************************************************************************************************");
		}
	}
	
	public static void imprime(String texto){
		try {
			escritor.write(texto + "\n");
			escritor.flush();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	private String geraTabelaVazao() {
		String tabela = "";
		DecimalFormat formatoNumero	=	new DecimalFormat("0.000000000000000000");
		tabela += "\n******************************************************** TABELA VAZAO(i) POR RODADA *********************************************************\n";	

		tabela += "|     RODADA      |";
		for (Host host : getListaHosts()) {
			tabela += "                  " + host + "                  |";
		}
		tabela += "\n";
		tabela +=("-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n");
		
		int rodada = 1;
		
		for (ArrayList<Double> listaVazaoRodada: getTabelaResultadosVazao()) {
			if (rodada < 10)
				tabela += "|   RODADA " + rodada + "     |";
			else
				tabela += "|   RODADA " + rodada + "   |";
			for (Double vazao: listaVazaoRodada) {
				tabela += "    " + formatoNumero.format(vazao) + "    |";
			}
			tabela += "\n";
			
			rodada++;
		}
		
		tabela += "|       MÉDIA       |";
		int numeroHost = 0;
		for (@SuppressWarnings("unused") Host listaHost : getListaHosts()) {
			double acumuladorVazao = 0;
			int numeroVazaoAcumulado = 0;
			
			for (ArrayList<Double> listaVazaoRodada: getTabelaResultadosVazao()) {
				acumuladorVazao += listaVazaoRodada.get(numeroHost);
				numeroVazaoAcumulado++;
			}
			
			tabela += "    " + formatoNumero.format((acumuladorVazao/numeroVazaoAcumulado)) + "    |";
			
			numeroHost++;
		}
		
		tabela += "\n**************************************************************************************************************************************************\n";
		
		return tabela;
	}

	private String imprimeUtilizacao() {
		return "************************************************************* UTILIZACAO DO MEIO ***************************************************************\n" + "TEMPO TOTAL OCUPADO: " + listaHosts.get(0).getTempoTotalMeioOcupadoUtilizacao() + "\nUTILIZACAO (p) DO CANAL: " + (listaHosts.get(0).getTempoTotalMeioOcupadoUtilizacao() / ((numeroRodadas * tempoRodada) - Constantes.TEMPO_FASE_TRANSIENTE)) + "\n**************************************************************************************************************************************************\n\n";
	}
	
	private String geraTabelaTap() {
		String tabela = "";
		DecimalFormat formatoNumero	=	new DecimalFormat("0.000000000000000000");
		
		tabela += "\n********************************************************** TABELA TAp(i) POR RODADA ***********************************************************\n";
		
		tabela += "|     RODADA      |";
		for (Host host : getListaHosts()) {
			tabela += "                  " + host + "                  |";
		}
		tabela += "\n";
		tabela +=("-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n");
		
		
		int rodada = 1;
		
		for (ArrayList<Double> listaTapiRodada: getTabelaResultadosTap()) {
			if (rodada < 10)
				tabela += "|   RODADA " + rodada + "     |";
			else
				tabela += "|   RODADA " + rodada + "   |";
			for (Double tapi: listaTapiRodada) {
				if (Double.isNaN(tapi))
					tabela += "                       --                       |";
				else
					tabela += "    " + formatoNumero.format(tapi) + "    |";
			}
			tabela += "\n";
			
			rodada++;
		}
		
		tabela += "|       MÉDIA       |";
		int numeroHost = 0;
		for (@SuppressWarnings("unused") Host listaHost : getListaHosts()) {
			double acumuladorTap = 0;
			int numeroTapiAcumulado = 0;
			
			for (ArrayList<Double> listaTapiRodada: getTabelaResultadosTap()) {
				acumuladorTap += listaTapiRodada.get(numeroHost);
				numeroTapiAcumulado++;
			}
			if (Double.isNaN(acumuladorTap))
				tabela += "                       --                       |";
			else
				tabela += "    " + formatoNumero.format((acumuladorTap/numeroTapiAcumulado)) + "    |";
			
			numeroHost++;
		}
		
		tabela += "\n**************************************************************************************************************************************************\n";
		
		return tabela;
	}
	
	private String geraTabelaTam() {
		String tabela = "";
		DecimalFormat formatoNumero	=	new DecimalFormat("0.000000000000000000");
		
		tabela += "\n********************************************************** TABELA TAm(i) POR RODADA ***********************************************************\n";
		
		tabela += "|     RODADA      |";
		for (Host host : getListaHosts()) {
			tabela += "                  " + host + "                  |";
		}
		tabela += "\n";
		tabela +=("-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n");
		
		
		int rodada = 1;
		
		for (ArrayList<Double> listaTamiRodada: getTabelaResultadosTam()) {
			if (rodada < 10)
				tabela += "|   RODADA " + rodada + "     |";
			else
				tabela += "|   RODADA " + rodada + "   |";
			for (Double tami: listaTamiRodada) {
				if (Double.isNaN(tami))
					tabela += "                       --                       |";
				else
					tabela += "   " + formatoNumero.format(tami) + "   |";
			}
			tabela += "\n";
			
			rodada++;
		}
		
		tabela += "|       MÉDIA       |";
		int numeroHost = 0;
		for (@SuppressWarnings("unused") Host listaHost : getListaHosts()) {
			double acumuladorTam = 0;
			int numeroTamiAcumulado = 0;
			
			for (ArrayList<Double> listaTamiRodada: getTabelaResultadosTam()) {
				acumuladorTam += listaTamiRodada.get(numeroHost);
				numeroTamiAcumulado++;
			}
			if (Double.isNaN(acumuladorTam))
				tabela += "                       --                       |";
			else
				tabela += "   " + formatoNumero.format((acumuladorTam/numeroTamiAcumulado)) + "   |";
			
			numeroHost++;
		}
		
		tabela += "\n**************************************************************************************************************************************************\n";
		
		return tabela;
	}
	
	private String geraTabelaNcm() {
		String tabela = "";
		DecimalFormat formatoNumero	=	new DecimalFormat("0.000000000000000000");
		
		tabela += "\n********************************************************* TABELA NCm(i) POR RODADA ***********************************************************\n";
		
		tabela += "|     RODADA      |";
		for (Host host : getListaHosts()) {
			tabela += "                  " + host + "                  |";
		}
		tabela += "\n";
		tabela +=("-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n");
		
		
		int rodada = 1;
		
		for (ArrayList<Double> listaNcmiRodada: getTabelaResultadosNcm()) {
			if (rodada < 10)
				tabela += "|   RODADA " + rodada + "     |";
			else
				tabela += "|   RODADA " + rodada + "   |";
			for (Double ncmi: listaNcmiRodada) {
				if (Double.isNaN(ncmi))
					tabela += "                       --                       |";
				else
					tabela += "    " + formatoNumero.format(ncmi) + "    |";
			}
			tabela += "\n";
			
			rodada++;
		}
		
		tabela += "|       MÉDIA       |";
		int numeroHost = 0;
		for (@SuppressWarnings("unused") Host listaHost : getListaHosts()) {
			double acumuladorNcm = 0;
			int numeroNcmiAcumulado = 0;
			
			for (ArrayList<Double> listaNcmiRodada: getTabelaResultadosNcm()) {
				acumuladorNcm += listaNcmiRodada.get(numeroHost);
				numeroNcmiAcumulado++;
			}
			
			if (Double.isNaN(acumuladorNcm))
				tabela += "                       --                       |";
			else
				tabela += "    " + formatoNumero.format((acumuladorNcm/numeroNcmiAcumulado)) + "    |";
			
			numeroHost++;
		}
		
		tabela += "\n**************************************************************************************************************************************************\n";
		
		return tabela;
	}

	private void executaSimulacao() {
		//Valor da rodada atual
		int rodadaAtual;
		Evento eventoAtual = this.getProximoEvento();
		
		//Ira iterar para cada rodada ate chegar ao numero desejado
		for (rodadaAtual = 1; rodadaAtual <= this.getNumeroRodadas(); rodadaAtual++) {
			//Ira avancar no tempo ate atingir o tempo limite de uma rodada
			if (eventoAtual != null) {
				setTempoAtual(eventoAtual.getTempoInicio());
				while (getTempoAtual() <= this.getTempoRodada() * rodadaAtual) {
					
					if (isFaseTransiente() && getTempoAtual() > Constantes.TEMPO_FASE_TRANSIENTE) {
						//Demarca o fim da fase transiente
						setFaseTransiente(false);
					}
					
					eventoAtual.executa();
					eventoAtual = this.getProximoEvento();
					if (eventoAtual != null)
						setTempoAtual(eventoAtual.getTempoInicio());
					else
						break;
				}
			}
			else {
				break;
			}
			ArrayList<Double> listaResultadosTapRodada = new ArrayList<Double>();
			ArrayList<Double> listaResultadosTamRodada = new ArrayList<Double>();
			ArrayList<Double> listaResultadosNcmRodada = new ArrayList<Double>();
			ArrayList<Double> listaResultadosVazaoRodada = new ArrayList<Double>();
			
			for (Host host : getListaHosts()) {
				//Calculando e armazenando estatistica TAP
				listaResultadosTapRodada.add(host.getTotalTAP() / host.getNumeroQuadrosTransmitidosTAP());
				host.setNumeroQuadrosTransmitidosTAP(0);
				host.setTotalTAP(0);
				
				//Calculando e armazenando estatistica TAM
				listaResultadosTamRodada.add(host.getTotalTAM() / host.getNumeroMensagensTransmitidasTAM());
				host.setNumeroMensagensTransmitidasTAM(0);
				host.setTotalTAM(0);
				
				//Calculando e armazenando estatistica NCM
				listaResultadosNcmRodada.add(host.getSomaMediasColisoesMensagemNCM() / host.getNumeroMensagensTransmitidasNCM());
				host.setNumeroMensagensTransmitidasNCM(0);
				host.setSomaMediasColisoesMensagemNCM(0);
				
				//Calculando e armazenando estatistica Vazao
				if ((rodadaAtual * tempoRodada) - Constantes.TEMPO_FASE_TRANSIENTE < Constantes.TEMPO_FASE_TRANSIENTE) {
					if ((rodadaAtual * tempoRodada) - Constantes.TEMPO_FASE_TRANSIENTE < 0) {
						listaResultadosVazaoRodada.add(0.0);
					}
					else {
						listaResultadosVazaoRodada.add(host.getNumeroTotalQuadrosTransmitidosVazao() / (tempoRodada - (Constantes.TEMPO_FASE_TRANSIENTE - ((rodadaAtual - 1) * tempoRodada))));
					}
				}
				else {
					listaResultadosVazaoRodada.add(host.getNumeroTotalQuadrosTransmitidosVazao() / tempoRodada);
				}
				host.setNumeroTotalQuadrosTransmitidosVazao(0);
			}
			
			this.getTabelaResultadosTap().add(listaResultadosTapRodada);
			this.getTabelaResultadosTam().add(listaResultadosTamRodada);
			this.getTabelaResultadosNcm().add(listaResultadosNcmRodada);
			this.getTabelaResultadosVazao().add(listaResultadosVazaoRodada);
		}
	}

	private Evento getProximoEvento() {
		return listaEventos.poll();
	}

	public static void adicionaEvento(Evento e) {
		listaEventos.add(e);
	}
	
	public static void removeEvento(Evento e) {
		listaEventos.remove(e);
	}

	public static ArrayList<Host> getListaHosts() {
		return listaHosts;
	}

	public void setListaHosts(ArrayList<Host> novaListaHosts) {
		listaHosts = novaListaHosts;
	}

	public double getTempoRodada() {
		return tempoRodada;
	}

	public void setTempoRodada(double tempoRodada) {
		this.tempoRodada = tempoRodada;
	}

	public int getNumeroRodadas() {
		return numeroRodadas;
	}

	public void setNumeroRodadas(int numeroRodadas) {
		this.numeroRodadas = numeroRodadas;
	}

	public static PriorityQueue<Evento> getListaEventos() {
		return listaEventos;
	}

	public static double getTempoAtual() {
		return tempoAtual;
	}

	public static void setTempoAtual(double tempoAtualNovo) {
		tempoAtual = tempoAtualNovo;
	}

	public static boolean isFaseTransiente() {
		return faseTransiente;
	}

	public static void setFaseTransiente(boolean faseTransienteNova) {
		faseTransiente = faseTransienteNova;
	}

	public ArrayList<ArrayList<Double>> getTabelaResultadosTap() {
		return tabelaResultadosTap;
	}

	public void setTabelaResultadosTap(
			ArrayList<ArrayList<Double>> tabelaResultadosTap) {
		this.tabelaResultadosTap = tabelaResultadosTap;
	}

	public ArrayList<ArrayList<Double>> getTabelaResultadosTam() {
		return tabelaResultadosTam;
	}

	public void setTabelaResultadosTam(
			ArrayList<ArrayList<Double>> tabelaResultadosTam) {
		this.tabelaResultadosTam = tabelaResultadosTam;
	}

	public ArrayList<ArrayList<Double>> getTabelaResultadosNcm() {
		return tabelaResultadosNcm;
	}

	public void setTabelaResultadosNcm(
			ArrayList<ArrayList<Double>> tabelaResultadosNcm) {
		this.tabelaResultadosNcm = tabelaResultadosNcm;
	}

	public ArrayList<ArrayList<Double>> getTabelaResultadosVazao() {
		return tabelaResultadosVazao;
	}

	public void setTabelaResultadosVazao(
			ArrayList<ArrayList<Double>> tabelaResultadosVazao) {
		this.tabelaResultadosVazao = tabelaResultadosVazao;
	}
	
}
